/*******************************************************************************
 * Copyright (c) 2010, 2018 西安秦晔信息科技有限公司
 * Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 *******************************************************************************/
package com.qinyeit.webapp.security.filter;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qinyeit.webapp.security.StatelessRequestSignToken;
import com.qinyetech.springstage.core.security.BodyReaderHttpServletRequestWrapper;
import com.qinyetech.springstage.core.security.TerminalDevice;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.SavedRequest;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.MediaType;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import static com.qinyeit.serviceapp.security.PrincipalIdentifierKey.PRINCIPAL_SECURITY_KEY;
import static com.qinyetech.springstage.core.security.SecurityHttpHeader.*;

/**
 * <p>ClassName: com.qinyeit.shirostarterdemo.security.filter.StatelessRequestSignValidationFilter
 * <p>Function: 请求签名验证，在这个过滤器之前，已经通过了身份认证
 * <p>date: 2018-08-13 18:13
 *
 * @author wuqing
 * @version 1.0
 * @since JDK 1.8
 */
@Slf4j
public class StatelessRequestSignValidationFilter extends AccessControlFilter {
    /**
     * 先执行：isAccessAllowed 再执行onAccessDenied
     *  isAccessAllowed：表示是否允许访问；mappedValue就是[urls]配置中拦截器参数部分，
     * 如果允许访问返回true，否则false；
     * 如果返回true的话，就直接返回交给下一个filter进行处理。
     *  如果返回false的话，回往下执行onAccessDenied
     * @param request
     * @param response
     * @param mappedValue
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return false;//所有的验证工作在 onAccessDenied中进行处理
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        log.debug("onAccessDenied.......");
        HttpServletRequest req=WebUtils.toHttp(request);
        HttpServletResponse res=WebUtils.toHttp(response);

        Subject subject=super.getSubject(request,response);
        String securityKey=getSecurityKey(subject);
        //没有通过登陆认证,没有密钥
        if(!subject.isAuthenticated() || !StringUtils.hasText(securityKey) ){
            sendResponseInfo(req,res);
            return false;
        }
        StatelessRequestSignToken token=createAuthToken(request);
        //验证请求头
        if(!token.baseValidate() || TerminalDevice.UNKNOWN==getTerminalDevice(request)){
            sendResponseInfo(req,res);
            return false;
        }
        token.setSecurityKey(securityKey);

        if(!token.signValidate()){
            sendResponseInfo(req,res);
            return false;
        }
        //放行
        return true;
    }

    /**
     * 读取请求实体
     * @param request
     * @return
     */
    private String getRequestBody(HttpServletRequest request){
        try (BufferedReader reader=new BufferedReader(new InputStreamReader(request.getInputStream(),StandardCharsets.UTF_8))){
            StringBuffer sb=new StringBuffer();
            reader.lines().forEach(sb::append);
            return sb.toString();
        } catch (IOException e) {
            log.error("read RequestBody error",e);
        }
        return null;
    }

    @Override
    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        if(isNeedWrapper(request)){
            super.doFilterInternal(new BodyReaderHttpServletRequestWrapper(WebUtils.toHttp(request)),response,chain);
        }else{
            super.doFilterInternal(request,response,chain);
        }
    }

    private boolean isNeedWrapper(ServletRequest request){
        HttpServletRequest req=WebUtils.toHttp(request);
        //只针对 json数据提交，并且是POST提交
        if(request.getContentType()!=null
                && request.getContentType().contains("application/json")
                && ("POST".equals(req.getMethod().toUpperCase()) || "PUT".equals(req.getMethod().toUpperCase()))){
            return true;
        }
        return false;
    }

    //生成无状态Token
    private StatelessRequestSignToken createAuthToken(ServletRequest request){
        if(!(request instanceof HttpServletRequest)){
            return null;
        }
        HttpServletRequest req=WebUtils.toHttp(request);
        //0、URI,不含主机和参数
        String requestUri=req.getHeader(X_ACCESS_URI);
        //1、用户身份
        String principal = req.getHeader(X_ACCESS_PRINCIPAL);
        //2、时间戳
        String timestamp = req.getHeader(X_ACCESS_TIMESTAMP);
        //3、客户端签名
        String sign = req.getHeader(X_ACCESS_SIGN);
        //4、客户端请求的参数列表
        Map<String, String[]> params = request.getParameterMap();
        if(isNeedWrapper(request)){
            return  new StatelessRequestSignToken(principal,timestamp,requestUri,sign,params,getRequestBody(req));
        }
        return new StatelessRequestSignToken(principal,timestamp,requestUri,sign,params);

    }
    private ObjectMapper mapper = new ObjectMapper();
    /**
     * ajax请求返回未认证信息
     */
    private void sendResponseInfo(HttpServletRequest request, HttpServletResponse response){
        //400非法请求
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        response.setContentType( MediaType.APPLICATION_JSON_UTF8_VALUE);
        SavedRequest savedRequest = new SavedRequest(request);
        Map<String,Object> errorResponse=new HashMap<>();
        errorResponse.put("success",false);
        errorResponse.put("message","非法请求");
        errorResponse.put("content",savedRequest);
        errorResponse.put("level","error");
        errorResponse.put("status",HttpServletResponse.SC_BAD_REQUEST);
        try {
            response.setCharacterEncoding("UTF-8");
            mapper.writer().writeValue(response.getWriter(),errorResponse);
        } catch (IOException e) {

        }
    }

    /**
     * 获取当前用户密钥
     * @param subject
     * @return
     */
    private String getSecurityKey(Subject subject){
        Map<String,Object> primaryPrincipal=getPrimaryPrincipal(subject);
        if(primaryPrincipal!=null){
            Object securityKey=primaryPrincipal.get(PRINCIPAL_SECURITY_KEY);
            return securityKey==null?null:securityKey.toString();
        }
        return  null;
    }

    private Map<String,Object> getPrimaryPrincipal(Subject subject){
        return (Map<String,Object>)SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
    }
    private TerminalDevice getTerminalDevice(ServletRequest request){
        String device=WebUtils.toHttp(request).getHeader(X_ACCESS_DEVICE);
        if(!StringUtils.hasLength(device)){
            return TerminalDevice.UNKNOWN;
        }
        try{
            return TerminalDevice.valueOf(device);
        }catch (Exception ex){
            log.warn("unknown device:",ex);
            return TerminalDevice.UNKNOWN;
        }

    }

}
